package cn.lili.elasticsearch;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.lili.common.exception.RetryException;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.client.RequestOptions;
import org.springframework.data.elasticsearch.core.ScriptType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * elasticsearch 索引抽象类
 * <p>
 * 定义一些索引常用方法
 *
 * @author paulG
 * @since 2022/8/5
 **/
@Slf4j
public abstract class ElasticsearchIndexAbstractService extends BaseElasticsearchService {

    private static final String IGNORE_FIELD = "serialVersionUID,promotionMap,id,goodsId";

    @Autowired
    private ElasticsearchOperations restTemplate;


    /**
     * 保存索引信息
     *
     * @param t 索引信息
     */
    public <T> T saveIndex(T t) {
        return this.restTemplate.save(t);
    }

    /**
     * 批量保存索引信息
     *
     * @param t 索引信息列表
     */
    public <T> Iterable<T> saveIndex(Iterable<T> t) {
        return this.restTemplate.save(t);
    }


    /**
     * 更新索引的的部分属性（只填写更新的字段，不需要更新的字段不要填写）
     *
     * @param id 商品索引id
     * @param t  要更新属性
     */
    public <T> void updateIndex(String id, T t) {
        T index = (T) this.findById(id, t.getClass());
        Map<String, Field> fieldMap = ReflectUtil.getFieldMap(t.getClass());
        // 通过反射获取全部字段，在根据参数字段是否为空，设置要更新的字段
        for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
            Object fieldValue = ReflectUtil.getFieldValue(t, entry.getValue());
            if (fieldValue != null && !IGNORE_FIELD.contains(entry.getKey())) {
                ReflectUtil.setFieldValue(index, entry.getValue(), fieldValue);
            }
        }
        this.saveIndex(index);
    }

    /**
     * 更新商品索引的的部分属性
     *
     * @param queryFields  查询字段
     * @param updateFields 更新字段
     * @param clazz        索引类
     */
    public <T> void updateIndexFields(Map<String, Object> queryFields, Map<String, Object> updateFields, Class<T> clazz) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        for (Map.Entry<String, Object> entry : queryFields.entrySet()) {
            TermQueryBuilder termQueryBuilder = new TermQueryBuilder(entry.getKey(), entry.getValue());
            nativeSearchQueryBuilder.withQuery(termQueryBuilder);
        }

        StringBuilder script = new StringBuilder();
        for (Map.Entry<String, Object> entry : updateFields.entrySet()) {
            script.append("ctx._source.").append(entry.getKey()).append("=").append("'").append(entry.getValue()).append("'").append(";");
        }
        UpdateQuery updateQuery = UpdateQuery.builder(nativeSearchQueryBuilder.build()).withScript(script.toString()).withLang("painless").withScriptType(ScriptType.INLINE).build();
        try {
            log.info("============updateQuery == "+ JSON.toJSONString(updateQuery));
            ByQueryResponse byQueryResponse = this.restTemplate.updateByQuery(updateQuery, this.restTemplate.getIndexCoordinatesFor(clazz));
            log.info("============updateIndexResult =="+ JSON.toJSONString(byQueryResponse));
            if (byQueryResponse.getVersionConflicts() > 0) {
                throw new RetryException("更新商品索引失败，es内容版本冲突");
            }
        } catch (Exception e) {
            log.error("更新商品索引异常", e);
        }
    }

    /**
     * 批量更新
     *
     * @param updateQueries 更新列表
     * @param clazz         索引类
     * @param <T>           索引类
     */
    public <T> void updateBatch(List<UpdateQuery> updateQueries, Class<T> clazz) {
        this.restTemplate.bulkUpdate(updateQueries, clazz);
    }

    /**
     * 根据id获取索引信息
     *
     * @param id    id
     * @param clazz 索引类
     * @return 商品索引信息
     */
    public <T> T findById(String id, Class<T> clazz) {
        return this.restTemplate.get(id, clazz);
    }

    /**
     * 批量获取索引信息
     *
     * @param query 查询条件
     * @param clazz 索引类
     * @return 索引信息列表
     */
    public <T> List<T> findByQuery(Query query, Class<T> clazz) {
        List<T> indexList = new ArrayList<>();
        for (SearchHit<T> search : this.restTemplate.search(query, clazz)) {
            indexList.add(search.getContent());
        }
        return indexList;
    }

    /**
     * 批量获取索引信息
     *
     * @param ids   索引ids
     * @param clazz 索引类
     * @return 索引信息列表
     */
    public <T> List<T> findByQuery(List<String> ids, Class<T> clazz) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(QueryBuilders.termsQuery("id", ids.toArray()));
        return this.findByQuery(queryBuilder.build(), clazz);
    }

    /**
     * 获取所有索引信息
     *
     * @param clazz 索引类
     * @return 索引信息列表
     */
    public <T> List<T> findAll(Class<T> clazz) {
        return this.findByQuery(this.restTemplate.matchAllQuery(), clazz);
    }

    /**
     * 根据字段查询删除索引
     *
     * @param queryFields 删除条件的字段（key为字段名，value为字段值）
     * @param clazz       索引类
     */
    public void deleteIndex(Map<String, Object> queryFields, Class<?> clazz) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        for (Map.Entry<String, Object> entry : queryFields.entrySet()) {
            //根据分类ID删除商品索引
            if(CharSequenceUtil.equals(entry.getKey(),"categoryId")){
                boolQueryBuilder.should(QueryBuilders.wildcardQuery("categoryPath", "*" + entry.getValue() + "*"));
            }else{
                boolQueryBuilder.must(QueryBuilders.termsQuery(entry.getKey(), entry.getValue()));
            }
        }

        DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest();
        deleteByQueryRequest.setQuery(boolQueryBuilder);
        deleteByQueryRequest.indices(this.restTemplate.indexOps(clazz).getIndexCoordinates().getIndexName());
        deleteByQueryRequest.setConflicts("proceed");
        try {
            BulkByScrollResponse bulkByScrollResponse = client.deleteByQuery(deleteByQueryRequest,
                    RequestOptions.DEFAULT);
            log.info("删除索引成功，删除数量：{}", bulkByScrollResponse);
            if (bulkByScrollResponse.getVersionConflicts() > 0) {
                throw new RetryException("删除索引失败，es内容版本冲突");
            }
        } catch (IOException e) {
            log.error("删除索引异常", e);
        }
    }


    /**
     * 根据索引id删除索引
     *
     * @param id    索引id
     * @param clazz 索引类
     * @param <T>   索引类
     * @return 删除结果
     */
    public <T> String deleteIndexById(String id, Class<T> clazz) {
        return this.restTemplate.delete(id, clazz);
    }


    /**
     * 根据索引id批量删除索引
     *
     * @param ids   索引id列表
     * @param clazz 索引类
     * @param <T>   索引类
     */
    public <T> void deleteIndexByIds(List<String> ids, Class<T> clazz) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(QueryBuilders.termsQuery("id", ids.toArray()));
        try {
            ByQueryResponse deleteResponse = this.restTemplate.delete(queryBuilder.build(), clazz);
            if (!deleteResponse.getFailures().isEmpty()) {
                log.error("删除索引出现错误：{}", deleteResponse.getFailures());
            }
        } catch (Exception e) {
            log.error("删除索引出现异常", e);
        }
    }

}
